Partnerzy
PolProg
Lomsel
KonradVme

Serwer sponsoruje

Certyfikaty

Valid HTML 4.01!
Valid CSS!

Delphi - komunikaty

Część: #2

Wstęp

Witam wszystkich po długiej przerwie (z mojej strony). Fanów przepraszam, wrogów zapraszam ;).
W pierwszej części artykułu "Delphi - komunikaty" obiecałem Wam, że wyjdzie następna, opatrzona numerkiem "2". Tak też się stało, więc zapraszam wszystkich programistów na małą wycieczkę po świecie windowsoskich komunikatów. Z mojego punktu widzenia będzie ona nieco ciekawsza od poprzedniej, gdyż dzisiaj pokaże kilka sztuczek z tej dziedziny.
Ostatnio jedynie przechwytywaliśmy komunikaty - czas by coś wysłać. Napiszemy proste programy, które będą umiały zamknąć inną aplikację jedynie znając jej nazwę. Będzie też coś dla zaawansowanych.

Wykorzystanie

Przykładów wykorzystania komunikatów jest wiele. Dziś skupimy się na tym co można osiągnąć wysyłając komunikat do innej aplikacji.
Wielu programistów na pewnym stopniu "rozwoju" staje przed problemem kontrolowania innych aplikacji z poziomu swojego programu. Do tego właśnie służą komunikaty. Za chwilę sprawimy, że nasza aplikacja zniknie. Potem zrobimy to samo z Microsoftoskim Kalkulatorem, na koniec pokażę coś fajnego.

Do wysyłania komunikatów służą trzy metody. Są to: SendMessage, PostMessage i Perform.

SendMessage
SendMessage jest funkcją. Zwraca ona wynik swojego działania. Po wysłaniu komunikatu, funkcja SendMessage czeka na jego wykonanie, a następnie zwraca wynik swojego działania.

SendMessage(hWnd: HWND; Msg: Cardinal; WParam: integer;
            LParam: integer); 

gdzie odpowiednio:

hWnd - uchwyt okna do którego wysyłamy komunikat
Msg - komunikat właściwy ;) tzn. nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to LongintParameter, czyli parametr 32-bitowy

PostMessage
PostMessage nie zwraca żadne wyniku swojego działania. Nie czeka ona na wykonanie wysłanego komunikatu.

PostMessage(hWnd: HWND; Msg: Cardinal; WParam: integer;
            LParam: integer); 

hWnd - uchwyt okna do którego wysyłamy komunikat
Msg - nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to LongintParameter, czyli parametr 32-bitowy

Perform
Służy do wysyłania komunikatów w obrębie naszej aplikacji. Za pomocą tej metody nie wyślemy komunikatu nigdzie indziej. Zauważmy, że nie ma ona parametru pozwalającego określić uchwyt aplikacji do której wysyłamy komunikat, dlatego też metoda Perform przyjmuje za domyślny uchwyt, uchwyt naszej aplikacji, czyli Application.Handle; Składnia Perform prezentuje się następująco:

Perform(Msg: Cardinal; WParam: integer; LParam: integer); 

Msg - nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to LongintParameter, czyli parametr 32-bitowy

Wysyłamy pierwszy komunikat

Od czego zaczniemy? Od zamykania okna? Dobra. Za chwilę sprawimy, że okno naszego programu zniknie! Nie próbujcie robić tego w domu ;)
Zanim jednak zaczniemy na dobre pisać programy wysyłające komunikaty, jestem zmuszony zapodać wam parę przykładowych nazw komunikatów i objaśnić do czego tak właściwie służą.
Jak już pewnie zdążyliście zauważyć, komunikaty zaczynają się charakterystycznym przedrostkiem (?) WM. Mamy więc: WM_CLOSE, WM_LBUTTONDOWN, WMRBUTTONDOWN, WM_UNDO, WM_DESTROY jak i wiele, wiele innych komunikatów czekających tylko na odkrycie. Znajdują się one w katalogu %katalog z delphi%\Source\Rtl\Win\Messages.pas , czyli np. w moim wypadku byłoby to: E:\Program Files\Borland Delphi 6\Source\Rtl\Win\Messages.pas . Macie tam zbiór wszystkich komunikatów. Jeszcze jedna rada : Wątpię, żeby ktokolwiek znał je wszystkie - po prostu - nie znacie komunikatu - zerkacie - i już wiecie, nie potrzeba się tego uczyć na pamięć zwłasza, że nie ma to najmniejszego sensu bo i tak cały czas plik macie w zasięgu ręki (jest niezbędny aby nasza aplikacja mogła otrzymywać i wysyłać komunikaty). I jeszcze jedno: gdy aplikacja nie obsługuje żadnych komunikatów jest jak warzywo ;) - nie dopuśćcie więc do tego!

Piszemy program

  1. Uruchamiamy środowisko Delphi.

  2. Na formularzu umieszczamy komponent Button (klikamy na palecie wybierając komponent, a następnie na formularzu)

  3. Gdy już na formularzu znajduje się przycisk, klikamy nań dwukrotnie otwierając tym samym edytor kodu (dostępny również przez naciśnięcie F12)

  4. Pomiędzy słowa:
    begin

    end;

    wpisujemy następującą instrukcję:
    Perform(WM_CLOSE,0,0);

Prawda, że łatwe? No jasne! Dlaczego wybrałem z tych trzech funkcji akurat Perform ? Ponieważ nie potrzeba wysyłać komunikatu poza naszą aplikację. Mamy za zadanie jedynie zakończyć działanie naszej aplikacji - nic więcej.

Tak. No właśnie. Ale co zrobić, żeby zamknąć inną aplikację, np. Kalkulator dołączany standardowo do Windowsów? Jest także sposób i na to, popatrzcie:

  1. Uruchamiamy środowisko Delphi.

  2. Na formularzu umieszczamy komponent Button (klikamy na palecie wybierając komponent, a następnie na formularzu)

  3. Gdy już na formularzu znajduje się przycisk, klikamy nań dwukrotnie otwierając tym samym edytor kodu (dostępny również przez naciśnięcie F12)

  4. Przed słówkiem
    begin
    wpisujemy słowo:

    var

    umieszczając następnie pod nim deklarację następującej zmiennej:

    uchwyt: HWND;

  5. Pomiędzy słówka
    begin

    end;


    wpisujemy instrukcję:

    uchwyt := FindWindow(nil,'Kalkulator');
    SendMessage(uchwyt,WM_CLOSE,0,0);

  6. Całość powinna wyglądać następująco :

    procedure TForm1.Button1Click(Sender: TObject);
    var
      uchwyt: HWND;
    begin
      uchwyt := FindWindow(nil, 'Kalkulator');
      SendMessage(uchwyt, WM_CLOSE, 0, 0);
    end;

Już zabieram się do tłumaczenia!
Najpierw deklarowana jest zmienna uchwyt, która będzie przechowywała uchwyt okna kalkulatora (każde okno w Windowsie ma swój uchwyt - to jest jakby identyfikator danego okna).
Następnie (po begin) do zmiennej uchwyt przypisywany jest za pomocą funkcji FindWindow() właśnie uchwyt okna kalkulatora. Następnie za pomocą funkcji SendMessage wysyłany jest komunikat zamknięcia ( WM_CLOSE ) do programu kalkulator. Zaraz, zaraz - powiecie - ale, jak działa funkcja...

...FindWindow

Funkcja FindWindow() prezentuje się następująco:

FindWindow(lpClassName: PChar; lpWindowName: PChar);

gdzie odpowiednio:

lpClassName - nazwa klasy do której należy okno
lpWindowName - nazwa okna (w naszym wypadku jest to 'Kalkulator';

O ile lpClassName nie będzie nam potrzebne (należy wpisać nil, ponieważ nie znamy jej) to lpWindowName spełnia tutaj ważną rolę. Otóż po podaniu nazwy okna 'Kalkulator' jako parametru, funkcja FindWindow zwraca nam uchwyt okna programu kalkulator. Sprytne? No, tak, ale wypadałoby jeszcze wysłać komunikat, który "powie" Kalkulatorowi, żeby się zamknął ;). Znając już najważniejsze parametry, wywołujemy funkcję SendMessage lub PostMessage i podstawiamy znane nam wartości. Gdy nie znamy jakiegoś parametru, wpisujemy w to miejsce 0 lub nil, w zależności czy dany parametr jest typu integer czy PChar.

Jeszcze raz komunikaty...

Komunikaty to dobry sposób również na komunikację w zakresie własnego programu. Mało osób wie, że można stworzyć również własne komunikaty. Tak! Delphi udostępnia nam i taką możliwość. Chcecie zobaczyć jak? Czytajcie dalej.
Dzięki własnoręcznie stworzonym komunikatom możemy przesyłać różnoraki informacje wewnątrz własnego programu. Nie jest to aż niezbędne do pisania programów, ale dowiedzieć się czegoś nowego zawsze można.
Aby napisać obsłużyć własny komunikat musimy po słówku uses, ale jeszcze przed słówkiem type (nie pytajcie dlaczego ;?) dopisać taką linię:

const
  Moj_komunikat = WM_USER + 100;

Następnie, do sekcji private dodajemy deklarację nowej procedury obsługującej komunikat:

  procedure MojKomunikat(var msg: TMessage);
  message Moj_komunikat;

a w sekcji implementation dodajemy nową procedurę i jej kod (kod który zostanie wywołany po wywołaniu komunikatu).
Procedura ta będzie pokazywała dwie tabliczki. Jedna będzie informowała, że udało się wywołanie własnego komunikatu, a druga wyświetli przekształcone na litery parametry dostarczone wraz z komunikatem (są to kody ASCII liter, a funkcja Chr() służy do zmiany kodów ASCII na litery np. dużej literze A w kodzie ASCII odpowiada kod 65). Którym literom odpowiadają poszczególne kody można łatwo sprawdzić korzystając ze specjalnych programów, tablic, lub po prostu na ślepo wstukując kody ( trzymając lewy Alt i wstukując kody na klawiaturze numerycznej ).

procedure TForm1.MojKomunikat(var msg : TMessage);
begin
  ShowMessage('Procedura zgłaszająca się po wywołaniu komunikatu
 "Moj_komunikat"');
  ShowMessage(Chr(Msg.WParam)+Chr(Msg.LParam));
end;

No tak, ale to jeszcze nie wszystko! 
Umieście na formularzu przycisk i dwukrotnie kliknijcie na niego w celu przeniesienia się do edytora kodu. W procedurze OnClick naszego przycisku wprowadźcie instrukcję, która będzie wywoływała procedurę MojKomunikat z parametrami 64,116 . Są to kody ASCII liter "@" i "t":

  Perform(Moj_komunikat, 64, 116);

W efekcie otrzymamy dwie tabliczki. Jedną z informacją, a drugą ... z napisem "@t". Teraz możecie już uruchomić program ( F9 ).
Działa? Na pewno!

Cały kod prezentuje się następująco:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;

const
  Moj_komunikat = WM_USER + 1987;

type
  TForm1 = class(TForm)
  Button1: TButton;
  procedure Button1Click(Sender: TObject);
  private
procedure MojKomunikat(var msg: TMessage);
  message Moj_komunikat;
  public
{ Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.MojKomunikat(var msg : TMessage);
begin
  ShowMessage('Procedura zgłaszająca sie po wywołaniu komunikatu
 "Moj_komunikat"');
  ShowMessage(Chr(Msg.WParam)+Chr(Msg.LParam));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Perform(Moj_komunikat,64,116);
end;

end.

Jak zamknąć inną aplikację znając jej ścieżkę?

Ok, ale obiecałem, że pokaże jeszcze jeden trick. Tym razem dla nieco bardziej zaawansowanych.
Jeżeli znasz ścieżkę uruchomionego programu to możesz zamknąć ją. Jak? Pokazuje to poniższy kod. Aha, do listy modułów uses musisz dodać słowo "TLHelp32".

var
  PHandle, FHandle: THandle;
  Process:TProcessEntry32;
  Done, Next: Boolean;
  EXE : String; // ścieżka programu
begin
  EXE := 'C:\Windows\Pulpit\prog.exe';
  FHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
  Process.dwSize := Sizeof(Process);
  Next := Process32First(FHandle,Process);
  while Next do
  begin{ jesli sciezka dostepu sie zgadza }
    if AnsiLowerCase(Process.szExeFile) = AnsiLowerCase(EXE) then
    begin
     PHandle:=OpenProcess(PROCESS_TERMINATE, False,
     Process.th32ProcessID);
     { to probujemy zabic aplikacje }
     Done := TerminateProcess(PHandle,0);
     if not Done then
       MessageBox(Handle, 'Błąd', 'Błąd', MB_OK);
     end;
    Next := Process32Next(FHandle,Process);
   end;
   CloseHandle(FHandle);
end;

Źródło: http://www.4programmers.net na FAQ (pozycja 58)

Zakończenie

Czym jednakże byłoby zakończenie bez przydatnej rady na przyszłość ? ;)
Mam dobrą wiadomość dla koderów edytorów tekstu i wszelkiego rodzaju innych paści operujących na tekście i komponencie Memo czy RichEdit. Można bowiem wysłać do kontrolki komunikat, aby cofnęła jedna wykonaną czynność. Niby nic, ale zawsze coś!
Aby Skonstruować takie jednopoziomowe polecenie Cofnij należy, jak już wspomniałem, dostarczyć komponentowi Memo1 odpowiedni komunikat - jaki ? WM_UNDO się kłania.

  1. Umieście na formie komponent Memo i komponent Button
  2. Komponent Button będzie służył do cofania ostatniej czynności w komponencie Memo ;)
  3. Dwukrotnie kliknijcie na komponent Button w przekonaniu, że otworzy się edytor kodu.
  4. Jeśli edytor kodu się nie otworzył - patrz: punkt 3 ;)
  5. Pomiędzy słowa:
    begin

    end;
    wpiszcie następującą instrukcję:

    PostMessage(Memo1.Handle, WM_UNDO, 0, 0);
  6. Polecenie Undo gotowe!

Gotowa procedura na polecenie Undo powinna wyglądać tak:

procedure TForm1.Button1Click(Sender: TObject);
begin
  PostMessage(Memo1.Handle, WM_UNDO, 0, 0);
end;

i nadszedł w końcu czas na...

Zakończenie właściwe

I tym oto miłym akcentem zakończę część drugą artykułu o meldunkach (inaczej komunikatach). Życzę wielu sukcesów w pisaniu coraz lepszych programów, działających nie tylko na komunikatach. Do artykułu dodaję archiwum z kodem źródłowym do zamykania kalkulatora ;). Pozdrawiam wszystkich czytelników zarówno tych którzy dotrwali do końca, jak i tych którzy skorzystali nieco wcześniej z funkcji "< Back".


Łukasz "Lukas" Wyporek
wypoker@poczta.onet.pl

 

Spis treści Redakcja @t Newsy
Software Hardware Internet Webmastering System Programowanie Grafika Telefonia Film Gry Magazyn Humor

Spis treści